package modeling;


/**

 * @author Mathieu Rousseau

 * @email mathieu.rousseau3@mail.mcgill.ca

 */


import java.io.BufferedReader;

import java.io.BufferedWriter;

import java.io.FileReader;

import java.io.FileWriter;

import java.io.IOException;

import java.util.ArrayList;
import java.util.Collections;

import java.util.Iterator;


/** ------------------------------------------------------------------------------------------------------------------
 * start of MicroarrayToIF class
 * ------------------------------------------------------------------------------------------------------------------  **/
public class MicroarrayToIF{
	

	/*  global variables  */
	private String filenameExperiment, filenameBAC;
	private String inputPath = "MicroarrayToIF/Input/";
	private String outputPath = "MicroarrayToIF/Output/";
	private ArrayList<ArrayList<Double>> microarrayExperiment, microarrayBAC, IFArray;
	private ArrayList<ArrayList<Integer>> indexMappings;
	private final String[] featureLengthArray = {"index1", "index2", "15", "18", "19", "20", "21", "22", "23", "24"};
	private double BACKGROUND_CUTOFF_PERCENTAGE = 150;
	private double STDEV_CUTOFF_PERCENTAGE = 100;
	private double normalizationFactor = 1.0;
	/*  end of global variables  */

	
	/*  start of getMicroarrayData method  */
	private void getMicroarrayData(String fileToOpen, ArrayList<ArrayList<Double>> arrayToFill){
		//	open and read the microarray data file
		String line = null;
		try{
			BufferedReader br = new BufferedReader(new FileReader(inputPath + fileToOpen));			
			while((line = br.readLine()) != null){
				String[] values = line.split("\t");
				if(values.length < 10){
					System.out.println("Error: there is an entry in \"" + inputPath + fileToOpen + "\" that does not have ten (10) values" +
							" (i.e. index1, index2, feature 1, feature 2, ..., feature 8)\nSystem.exit(0)");
					System.exit(0);
				}
				ArrayList<Double> rowEntry = new ArrayList<Double>(10);
				for(int i=0; i<10; i++){
					rowEntry.add(Double.parseDouble(values[i]));
				}
				rowEntry.trimToSize();
				arrayToFill.add(rowEntry);
			}
			br.close();
		}catch (IOException e) {
			e.printStackTrace();
		}
		arrayToFill.trimToSize();
	}/*  end of getMicroarrayData method  */

	
	/*  start of removeOutliers method  */
	private void removeOutliers(ArrayList<ArrayList<Double>> arrayToClean, String filename){
		String outputFilename = outputPath + "outliers_background_cutoff_" + filename;
		try{			
			BufferedWriter bw = new BufferedWriter(new FileWriter(outputFilename));

			for(ArrayList<Double> rowEntry: arrayToClean){
				double background = rowEntry.get(2);			//	use feature length 15 as the background signal
				int index = 0;
				for(int j=9; j>2; j--){
					if((rowEntry.get(j) * 100 / background) < BACKGROUND_CUTOFF_PERCENTAGE){					//	remove points with signal < CUTOFF_PERCENTAGE of the background signal
						bw.write(rowEntry.get(0).intValue() + "-" + rowEntry.get(1).intValue() + " removed value " + rowEntry.get(j) + " for feature length " +
								featureLengthArray[j] +	" because " + (rowEntry.get(j) * 100 / background) + "% < " + BACKGROUND_CUTOFF_PERCENTAGE + "%");
						bw.flush();
						bw.newLine();
						rowEntry.set(j, 0.0);
						index = j - 1;
						break;
					}
				}
				while(index > 2){				//	remove all points for features shorter than the longest feature length found and removed
					bw.write("\t" + rowEntry.get(0).intValue() + "-" + rowEntry.get(1).intValue() + " also removed value " + rowEntry.get(index) +
							" for feature length " + featureLengthArray[index]);
					bw.flush();
					bw.newLine();
					rowEntry.set(index, 0.0);
					index--;
				}
			}
			bw.flush();
			bw.close();
			
			
			//	remove all of the background signals by subtracting the value for the 15bp probe from the rest of the non-zero values
			for(ArrayList<Double> rowEntry: arrayToClean){
				for(int i=9; i>1; i--){			//  start from the end and remove the background signal (15bp-15bp=0), don't need it further
					if(rowEntry.get(i) != 0){
						rowEntry.set(i, rowEntry.get(i)-rowEntry.get(2));
					}
				}
			}
			
		}catch (IOException e) {
			e.printStackTrace();
		}//end of try-catch block
	System.out.println("\nOutput file: " + outputFilename);
	}/*  end of removeOutliers method  */

	
	/*  start of matchWithBAC method  */
	private void matchWithBAC(){
		String outputFilename = outputPath + "outliers_BAC_value_missing_" + filenameExperiment;
		try{			
			BufferedWriter bw = new BufferedWriter(new FileWriter(outputFilename));

			for(int i=0; i<microarrayBAC.size(); i++){
				ArrayList<Double> rowEntryBAC = microarrayBAC.get(i);
				ArrayList<Double> rowEntryToMatch = microarrayExperiment.get(i);
				for(int j=2; j<rowEntryBAC.size(); j++){
					if((rowEntryBAC.get(j) == 0.0) && (rowEntryToMatch.get(j) != 0.0)){
						bw.write(rowEntryToMatch.get(0).intValue() + "-" + rowEntryToMatch.get(1).intValue() + " removed value " + rowEntryToMatch.get(j) +
								" for feature length " + featureLengthArray[j] + " because BAC = " + rowEntryBAC.get(j));
						bw.flush();
						bw.newLine();
						rowEntryToMatch.set(j, 0.0);
					}
				}
			}
			bw.flush();
			bw.close();
		}catch (IOException e) {
			e.printStackTrace();
		}//end of try-catch block
	System.out.println("\nOutput file: " + outputFilename);
	}/*  end of matchWithBAC method  */

	
	/*  start of outputCleanedInputFile method  */
	private void outputCleanedInputFile(ArrayList<ArrayList<Double>> arrayToOuput, String filename){
		String outputFilename = outputPath + "cleaned_input_file_" + filename;
		try{			
			BufferedWriter bw = new BufferedWriter(new FileWriter(outputFilename));

			for(ArrayList<Double> rowEntry: arrayToOuput){
				for(int i=0; i<10; i++){
					if(i < 2){
						bw.write(rowEntry.get(i).intValue() + "\t");
					}
					else if(i == 9){
						bw.write(String.valueOf(rowEntry.get(i)));
					}
					else{
						bw.write(rowEntry.get(i) + "\t");
					}
				}
				bw.newLine();
				bw.flush();
			}
			bw.close();
		}catch (IOException e) {
			e.printStackTrace();
		}//end of try-catch block
	System.out.println("\nOutput file: " + outputFilename);
	}/*  end of outputCleanedInputFile method  */
	

	/*  start of calculateIF method  */
	private void calculateIF(){
		
		IFArray = new ArrayList<ArrayList<Double>>();
		
		for(int i=0; i<microarrayExperiment.size(); i++){
			ArrayList<Double> ratios = new ArrayList<Double>(0);
			for(int j=3; j<microarrayExperiment.get(i).size(); j++){
				if(microarrayExperiment.get(i).get(j) != 0.0){
					if(microarrayBAC.get(i).get(j) == 0){
						System.out.println("Error:cannot divide by zero\nSystem.exit(0)");
						System.exit(0);
					}
					ratios.add((double)microarrayExperiment.get(i).get(j) / (double)microarrayBAC.get(i).get(j));
				}
			}

			ratios.trimToSize();
			
			double averageIF = 0.0;
			
			if(ratios.size() > 0){
				Iterator<Double> ratiosItr = ratios.iterator();
				while(ratiosItr.hasNext()){
					averageIF += ratiosItr.next();
				}
				averageIF = averageIF / (double)ratios.size();
			}
			
			//  if the IF value needs to be normalized, multiply it by the normalization factor
			if(normalizationFactor != 1){
//				System.out.println("Normalizing with factor (" + normalizationFactor + ")\t" + averageIF + "  -->  " + (averageIF*normalizationFactor));
				averageIF *= normalizationFactor;
			}
			
			double stdevIF = 0.0;
			
			if(ratios.size() > 2){
				Iterator<Double> ratiosItr = ratios.iterator();				
				while(ratiosItr.hasNext()){
					stdevIF += Math.pow(ratiosItr.next() - averageIF,2);
				}
				stdevIF = stdevIF / ((double)ratios.size() - 1);
				stdevIF = Math.sqrt(stdevIF);
			}

			ArrayList<Double> rowEntryIF = new ArrayList<Double>(5);
			rowEntryIF.add(microarrayExperiment.get(i).get(0));
			rowEntryIF.add(microarrayExperiment.get(i).get(1));
			rowEntryIF.add(averageIF);
			rowEntryIF.add(stdevIF);
			rowEntryIF.add((double)ratios.size());
			rowEntryIF.trimToSize();
			IFArray.add(rowEntryIF);
		}
		IFArray.trimToSize();
	}/*  end of calculateIF method  */

	
	/*  start of removeIFOutliers method  */
	private void removeIFOutliers(){
		String outputFilename = outputPath + "outliers_stdev_bigger_than_IF_" + filenameExperiment;
		try{			
			BufferedWriter bw = new BufferedWriter(new FileWriter(outputFilename));

			bw.write("standard deviation cutoff value = " + STDEV_CUTOFF_PERCENTAGE + "%");
			bw.newLine();
			bw.flush();
			
			//	remove any average IF value that is zero or less than or equal to its standard deviation value
			ArrayList<ArrayList<Double>> tempIFArray = new ArrayList<ArrayList<Double>>(IFArray.size());
			for(ArrayList<Double> rowEntry: IFArray){
				
				double IF = rowEntry.get(2);
				double stdev = rowEntry.get(3);
				
				double stdevPercentOfIF = 100.0 * stdev / IF;
				
				if((IF > 0.0) && (stdevPercentOfIF < STDEV_CUTOFF_PERCENTAGE)){
					tempIFArray.add(rowEntry);
				}
				else{
					bw.write("Removed entry: " + rowEntry.get(0).intValue() + "-" + rowEntry.get(1).intValue() +
							"\t" + "IF= " + rowEntry.get(2) + "\tstdev= " + rowEntry.get(3) + "\t" + (100.0*rowEntry.get(3)/rowEntry.get(2)) + "%");
					bw.flush();
					bw.newLine();
				}
			}
			tempIFArray.trimToSize();
			IFArray = tempIFArray;
			bw.flush();
			bw.close();
		}catch (IOException e) {
			e.printStackTrace();
		}//end of try-catch block
		System.out.println("\nOutput file: " + outputFilename);
	}/*  end of removeIFOutliers method  */

	
	/*  start of displayIF method  */
	private void displayIF(){
		Iterator<ArrayList<Double>> IFItr = IFArray.iterator();
		while(IFItr.hasNext()){
			Iterator<Double> rowItr = IFItr.next().iterator();
			while(rowItr.hasNext()){
				System.out.print(rowItr.next() + "\t");
			}
			System.out.println();
		}
	}/*  end of displayIF method  */

	
	/*  start of outputIF method  */
	private void outputIF(String filename){
		String outputFilename = outputPath + "IF_" + filename + "_"+ filenameExperiment;
		try{
			BufferedWriter bw = new BufferedWriter(new FileWriter(outputFilename));
			for(ArrayList<Double> rowEntry: IFArray){
				for(int i=0; i<rowEntry.size(); i++){
					if(i < 2){									//  the fragment indices
						bw.write(rowEntry.get(i).intValue() + "\t");
					}
					else if(i == rowEntry.size()-1){			//  the count (an integer value)
						bw.write(String.valueOf(rowEntry.get(i).intValue()));
					}
					else{										//  the IF and stdev values
						bw.write(rowEntry.get(i) + "\t");
					}
				}
				bw.flush();
				bw.newLine();
			}
			bw.close();
		}catch (IOException e) {
			e.printStackTrace();
		}//end of try-catch block
		System.out.println("\nOutput file: " + outputFilename);
	}/*  end of outputIF method  */
	
	
	/*  start of method mapFragmentIndices  */
	private void mapFragmentIndices(){
		
		ArrayList<Integer> fragmentIndices = new ArrayList<Integer>();
		
		for(ArrayList<Double> rowEntry: IFArray){
			if(!fragmentIndices.contains(rowEntry.get(0).intValue())){
				fragmentIndices.add(rowEntry.get(0).intValue());
			}
			if(!fragmentIndices.contains(rowEntry.get(1).intValue())){
				fragmentIndices.add(rowEntry.get(1).intValue());
			}			
		}
		fragmentIndices.trimToSize();
		
		//	sort the fragment indices in increasing order
		Collections.sort(fragmentIndices);
		
		indexMappings = new ArrayList<ArrayList<Integer>>(Collections.max(fragmentIndices).intValue() - Collections.min(fragmentIndices).intValue() + 1);
		Iterator<Integer> fragIndItr = fragmentIndices.iterator();
		while(fragIndItr.hasNext()){
			ArrayList<Integer> mappingEntry = new ArrayList<Integer>();
			mappingEntry.add(fragIndItr.next());
			indexMappings.add(mappingEntry);
		}
		indexMappings.trimToSize();
		
		//	fill in the missing index fragments
		for(int i=0; i<indexMappings.size(); i++){
			if(i != (indexMappings.size()-1)){									//	not the last entry in the list
				int currentEntry = indexMappings.get(i).get(0);
				int nextEntry = indexMappings.get(i+1).get(0);
				while(currentEntry != (nextEntry-1)){
					indexMappings.get(i).add(currentEntry+1);
					currentEntry++;
				}
			}
		}

		//	create a mapping array for fast renumbering
		double mappingArray[] = new double[Collections.max(fragmentIndices) + 1];
		for(int i=0; i<indexMappings.size(); i++){
			Iterator<Integer> mapRowItr = indexMappings.get(i).iterator();
			while(mapRowItr.hasNext()){
				mappingArray[mapRowItr.next()] = (i+1);
			}
		}
		
		//	replace all the fragment indices by their mapped values
		Iterator<ArrayList<Double>> IFArrayItr = IFArray.iterator();
		while(IFArrayItr.hasNext()){
			ArrayList<Double> IFrowEntry = IFArrayItr.next();
			IFrowEntry.set(0, mappingArray[IFrowEntry.get(0).intValue()]);
			IFrowEntry.set(1, mappingArray[IFrowEntry.get(1).intValue()]);
		}
	}
	/*  end of method mapFragmentIndices  */
	

	/*  start of outputHeatMap method  */
	private void outputHeatMap(String filename){
		//	create a temporary heatmap array that will then be output
		int minIndex = IFArray.get(0).get(0).intValue();
		int maxIndex = IFArray.get(0).get(0).intValue();
		for(ArrayList<Double> rowEntry: IFArray){
			if(rowEntry.get(0) < minIndex){
				minIndex = rowEntry.get(0).intValue();
			}
			if(rowEntry.get(1) < minIndex){
				minIndex = rowEntry.get(1).intValue();
			}
			if(rowEntry.get(0) > maxIndex){
				maxIndex = rowEntry.get(0).intValue();
			}
			if(rowEntry.get(1) > maxIndex){
				maxIndex = rowEntry.get(1).intValue();
			}
		}

		int sizeOfHeatMap = maxIndex - minIndex + 1;

		double[][] heatMap = new double[sizeOfHeatMap][sizeOfHeatMap];
		for(ArrayList<Double> rowEntry: IFArray){
			if(rowEntry.get(0) < rowEntry.get(1)){
				heatMap[rowEntry.get(0).intValue()-minIndex][rowEntry.get(1).intValue()-minIndex] = rowEntry.get(2);
			}
			else{
				heatMap[rowEntry.get(1).intValue()-minIndex][rowEntry.get(0).intValue()-minIndex] = rowEntry.get(2);
			}
		}

		
		//	output the heatmap to file
		String outputFilename = outputPath + "IF_heatmap_" + filename + "_" + filenameExperiment;
		try{			
			BufferedWriter bw = new BufferedWriter(new FileWriter(outputFilename));
			bw.write("\t");
			for(int i=minIndex; i<=maxIndex; i++){
				bw.write(i + "\t");
			}
			bw.flush();
			bw.newLine();
			for(int i=0; i<sizeOfHeatMap; i++){
				bw.write(i+minIndex + "\t");
				for(int j=0; j<sizeOfHeatMap; j++){
					if(heatMap[i][j] != 0.0){
						bw.write(heatMap[i][j] + "\t");
					}
					else{
						bw.write("\t");
					}
				}
				bw.flush();
				bw.newLine();
			}
			bw.close();
		}catch (IOException e) {
			e.printStackTrace();
		}//end of try-catch block
		System.out.println("\nOutput file: " + outputFilename);
	}/*  end of outputHeatMap method  */


	/*  start of method ouputFragmentMapping  */
	private void outputFragmentMapping(){
		String outputFilename = outputPath + "fragmentMapping_" + filenameExperiment;
		try{
			BufferedWriter bw = new BufferedWriter(new FileWriter(outputFilename));
			for(int i=0; i<indexMappings.size(); i++){
				bw.write((i+1) + "\t");
				ArrayList<Integer> rowEntry = indexMappings.get(i);
				for(int j=0; j<rowEntry.size(); j++){
					if(j != rowEntry.size()-1){
						bw.write(String.valueOf(rowEntry.get(j).intValue()) + "\t");						
					}
					else{
						bw.write(String.valueOf(rowEntry.get(j)));
					}
				}
				bw.newLine();
				bw.flush();
			}
			bw.close();
		}catch (IOException e) {
			e.printStackTrace();
		}//end of try-catch block
		System.out.println("\nOutput file: " + outputFilename);
	}/*  start of method ouputFragmentMapping  */
	
	
	/*  start of run method  */
	public void run(String[] args){
		if(args.length == 4){
			filenameExperiment = args[0];
			filenameBAC = args[1];
			BACKGROUND_CUTOFF_PERCENTAGE = Double.valueOf(args[2]);
			STDEV_CUTOFF_PERCENTAGE = Double.parseDouble(args[3]);
		}
		else if(args.length == 5){
			filenameExperiment = args[0];
			filenameBAC = args[1];
			BACKGROUND_CUTOFF_PERCENTAGE = Double.valueOf(args[2]);
			STDEV_CUTOFF_PERCENTAGE = Double.parseDouble(args[3]);
			normalizationFactor = Double.parseDouble(args[4]);
		}
		else{
			System.out.println("Error: please use the following pattern (without the quotes)\n" +
					"\t\"java MicroarrayToIF ExperimentName.txt BACname.txt BACKGROUND_CUTOFF_PERCENTAGE STDEV_CUTOFF_PERCENTAGE\"\n" +
					"System.exit(0)");
			System.exit(0);
		}
		
		
		System.out.println("Input file used as numerator for IF calculations: " + inputPath + filenameExperiment);
		System.out.println("Input file used as denominator for IF calculations: " + inputPath + filenameBAC);
		
		System.out.println("\nUsing background cutoff of: " + BACKGROUND_CUTOFF_PERCENTAGE + "%");
		System.out.println("\nUsing standard deviation cutoff of: " + STDEV_CUTOFF_PERCENTAGE + "%");
		
		microarrayExperiment = new ArrayList<ArrayList<Double>>();
		microarrayBAC = new ArrayList<ArrayList<Double>>();

		getMicroarrayData(filenameExperiment, microarrayExperiment);
		getMicroarrayData(filenameBAC, microarrayBAC);

		removeOutliers(microarrayExperiment, filenameExperiment);
		removeOutliers(microarrayBAC, filenameBAC);

		matchWithBAC();

		outputCleanedInputFile(microarrayExperiment, filenameExperiment);
		outputCleanedInputFile(microarrayBAC, filenameBAC);
		
		calculateIF();

		removeIFOutliers();

		outputIF("original");

		outputHeatMap("original");
		
		
		//  renumber the fragments starting from index one (1)
		mapFragmentIndices();			//	map the unique fragment indices in the data so they are sequential

		outputFragmentMapping();		//	output the fragment mapping for future reference

		outputIF("renumbered");			//	output the IFs with their renumbered fragment indices
		
		outputHeatMap("renumbered");
		
	}/*  end of run method  */	
}/*  end of MicroarrayToIF class  */